﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Principal;

namespace SecurityAgent.Client
{
  
    public interface IUserManager
    {
        bool Authenticate(string applicationKey, string userName, string password, out string userToken);
        
        bool IsInRole(string applicationKey, string userToken, string userName, string role);
        
        string[] GetRoles(string applicationKey, string currentToken, string userName);
        
        AccessLevel CheckObjectAccess(string applicationKey, string currentToken, string objectName);
        
        ObjectClaim[] GetClaims(string applicationKey, string currentToken, string category);
        
        ObjectClaim[] GetClaims(string applicationKey, string currentToken);

        ProfilePropertyValueCollection GetPropertyValues(String applicationKey, String userToken, String UserName, string[] PropertyNames);
       
        bool SetPropertyValues(string applicationKey, string userToken, string userName, ProfilePropertyValue[] values);
        
    }



    /// <summary>
    /// Used to replace RolePrivder if no role provider is to be used
    /// </summary>
    /// 
    [Serializable]
    public class SASPrincipal : IPrincipal
    {
        private IIdentity _identity;

        IPrincipal m_OldPrincipal;
        string m_ApplicationName;
        string m_currentToken;
        static bool m_ThreadPolicySet = false;

        IUserManager m_userManager;

        public IIdentity Identity
        {
            get
            {
                return _identity;
            }
        }

        public string[] Roles { get; set; }

        public ObjectClaim[] Claims { get; set; }

        public SASPrincipal(IIdentity identity, IUserManager userManager, string applicationKey, string currentToken)
        {
            _identity = identity;
            m_ApplicationName = applicationKey;
            m_currentToken = currentToken;

            m_userManager = userManager;

            m_OldPrincipal = System.Threading.Thread.CurrentPrincipal;
            System.Threading.Thread.CurrentPrincipal = this;
        }

        public SASPrincipal(IIdentity identity, IUserManager userManager, string applicationKey, string currentToken, bool cacheRoles, bool cacheClaims) : this(identity, userManager, applicationKey, currentToken )
        {
            if (cacheRoles)
                Roles = m_userManager.GetRoles(applicationKey, currentToken, identity.Name);

            if (cacheClaims)
                Claims = m_userManager.GetClaims(applicationKey, currentToken);
        }


        static public bool IsLoggedIn
        {
            get { return System.Threading.Thread.CurrentPrincipal is SASPrincipal; }
        }

        static public SASPrincipal Current
        {
            get
            {
                if (System.Threading.Thread.CurrentPrincipal is SASPrincipal)
                    return System.Threading.Thread.CurrentPrincipal as SASPrincipal;
                else
                    return null;
            }
        }

        
        public bool IsInRole(string role)
        {
            if (Roles != null)
            {
                return Array.BinarySearch(Roles, role) >= 0 ? true : false;
            }
            else
            {
                return m_userManager.IsInRole(m_ApplicationName, m_currentToken, _identity.Name, role);
            }
        }

        public AccessLevel CheckObjectAccess(string objectName)
        {
            if (Claims != null && Claims.Count() > 0)
            {
                foreach (ObjectClaim c in Claims)
                {
                    if (c.ObjectName == objectName)
                        return c.ObjectAccess;
                }
            }
            else
            {
                return m_userManager.CheckObjectAccess(m_ApplicationName, m_currentToken, objectName);
            }

            return null;
        }

        public ObjectClaim[] ListClaims(string category, AccessLevel requiredAccess)
        {
            List<ObjectClaim> ret = new List<ObjectClaim>();
            ObjectClaim[] l_claims = null;
            if (Claims != null && Claims.Count() > 0)
            {
                l_claims = Claims;
            }
            else
            {
                l_claims = m_userManager.GetClaims(m_ApplicationName, m_currentToken, category);
            }

            if (l_claims == null || l_claims.Count() == 0) return null;

            foreach (ObjectClaim oc in l_claims)
            {
                if (string.IsNullOrEmpty(category) || oc.ObjectCategory == category)
                {
                    if (requiredAccess == null || VerifyAccess(oc.ObjectAccess, requiredAccess))
                    {
                        ret.Add(oc);
                    }
                }
            }

            return ret.ToArray();

        }

        public string GetProfilePropertyValue(string propertyName)
        {
            ProfilePropertyValueCollection ret = m_userManager.GetPropertyValues(m_ApplicationName, m_currentToken, Identity.Name, new string[] { propertyName });
            if (ret == null || ret.Count == 0) return null;

            return ret[0].PropertyValue;
        }

        public bool SetProfilePropertyValue(string propertyName, object propertyValue)
        {
            ProfilePropertyValue value = new ProfilePropertyValue{ PropertyName = propertyName, PropertyValue = propertyValue.ToString() };
            return m_userManager.SetPropertyValues(m_ApplicationName, m_currentToken, Identity.Name, new ProfilePropertyValue[] { value });
        }

        static public void Attach(IIdentity user, IUserManager userManager, string applicationName, string userToken)
        {
            Attach(user, userManager, applicationName, userToken, false, false);
        }

        static public void Attach(IIdentity user, IUserManager userManager, string applicationName, string userToken, bool cacheRoles, bool cacheClaims)
        {

            IPrincipal customPrincipal = new SASPrincipal(user, 
                    userManager, applicationName, userToken, cacheRoles, cacheClaims );

            //Make sure all future threads in this app domain use this principal
            //In addition, because default principal cannot be set twice:
            if (m_ThreadPolicySet == false)
            {
                AppDomain currentDomain = AppDomain.CurrentDomain;
                currentDomain.SetThreadPrincipal(customPrincipal);
                m_ThreadPolicySet = true;
            }
        }

        public void Detach()
        {
            System.Threading.Thread.CurrentPrincipal = m_OldPrincipal;
        }

        private static bool VerifyAccess(AccessLevel provided, AccessLevel requested)
        {
            if (provided == null || requested == null) return false;

            return ((!requested.AllowCreate) || provided.AllowCreate)
                && ((!requested.AllowRead) || provided.AllowRead)
                && ((!requested.AllowDelete) || provided.AllowDelete)
                && ((!requested.AllowUpdate) || provided.AllowUpdate);
        }
    }

}
